package main

import (
	"context"
	"fmt"
	"github.com/gofiber/fiber"

	// "fmt"
	"testing"
	"time"

	"gitee.com/chunanyong/zorm"

	//00.引入数据库驱动
	_ "github.com/go-sql-driver/mysql"
)

const studentTableName = "student"
type Student struct{

	zorm.EntityStruct

	Id string `column:"s_id"`
	Name string `column:"s_name"`
	Birth string `column:"s_birth"`
	Sex string `column:"s_sex"`
	CreateTime time.Time `column:"create_time"`
	UpdateTime time.Time `column:"update_time"`
	CourseList []Course
}
//GetTableName 获取表名称
func (entity *Student) GetTableName() string {
	return studentTableName
}
//GetPKColumnName 获取数据库表的主键字段名称.因为要兼容Map,只能是数据库的字段名称.
func (entity *Student) GetPKColumnName() string {
	return "s_id"
}
//newDemoStruct 创建一个默认对象
func newStudent() Student {

	fmt.Println("UUID：", zorm.FuncGenerateStringID())

	demo := Student{
		Id: zorm.FuncGenerateStringID(),
		Name: "赵牧神",
		Birth: "180-01-01",
		Sex: "男",
		CreateTime: time.Now(),
		UpdateTime: time.Now(),
	}
	return demo
}

const courseTableName = "course"
type Course struct{

	zorm.EntityStruct

	Id string `column:"c_id"`
	Name string `column:"c_name"`
	TeacherId string `column:"t_id"`
	CreateTime time.Time `column:"create_time"`
	UpdateTime time.Time `column:"update_time"`
	Score Score
	Teacher Teacher
}
//GetTableName 获取表名称
func (entity *Course) GetTableName() string {
	return courseTableName
}
//GetPKColumnName 获取数据库表的主键字段名称.因为要兼容Map,只能是数据库的字段名称.
func (entity *Course) GetPKColumnName() string {
	return "c_id"
}

const teacherTableName = "teacher"
type Teacher struct{

	zorm.EntityStruct

	Id string `column:"t_id"`
	Name string `column:"t_name"`
	CreateTime time.Time `column:"create_time"`
	UpdateTime time.Time `column:"update_time"`
}
//GetTableName 获取表名称
func (entity *Teacher) GetTableName() string {
	return teacherTableName
}
//GetPKColumnName 获取数据库表的主键字段名称.因为要兼容Map,只能是数据库的字段名称.
func (entity *Teacher) GetPKColumnName() string {
	return "t_id"
}


const scoreTableName = "score"
type Score struct{

	zorm.EntityStruct

	StudentId string `column:"s_id"`
	CourseId  string `column:"t_id"`
	Score string `column:"s_score"`
	CreateTime time.Time `column:"create_time"`
	UpdateTime time.Time `column:"update_time"`
}
//GetTableName 获取表名称
func (entity *Score) GetTableName() string {
	return scoreTableName
}

//dbDao 代表一个数据库,如果有多个数据库,就对应声明多个DBDao
var dbDao *zorm.DBDao

// ctx默认应该有 web层传入,例如gin的c.Request.Context().这里只是模拟
var ctx = context.Background()

//01.初始化DBDao
func init() {

	println("连接mysql。。。。。。。。。。。。。。。。。。。")

	//自定义zorm日志输出
	//zorm.LogCalldepth = 4 //日志调用的层级
	//zorm.FuncLogError = myFuncLogError //记录异常日志的函数
	//zorm.FuncLogPanic = myFuncLogPanic //记录panic日志,默认使用ZormErrorLog实现
	//zorm.FuncPrintSQL = myFuncPrintSQL //打印sql的函数

	//dbDaoConfig 数据库的配置
	dbDaoConfig := zorm.DataSourceConfig{
		//DSN 数据库的连接字符串
		DSN: "root:new_password@tcp(127.0.0.1:3306)/zorm?charset=utf8&parseTime=true",
		//DriverName 数据库驱动名称,和DBType对应,一个数据库可以有多个驱动(DriverName)
		DriverName: "mysql",
		//DBType 数据库类型(mysql,postgresql,oracle,mssql,sqlite),zorm判断方言的依据,一个数据库可以有多个驱动(DriverName)
		DBType: "mysql",
		//MaxOpenConns 数据库最大连接数 默认50
		MaxOpenConns: 50,
		//MaxIdleConns 数据库最大空闲连接数 默认50
		MaxIdleConns: 50,
		//ConnMaxLifetimeSecond 连接存活秒时间. 默认600(10分钟)后连接被销毁重建.避免数据库主动断开连接,造成死连接.MySQL默认wait_timeout 28800秒(8小时)
		ConnMaxLifetimeSecond: 600,
		//PrintSQL 打印SQL.会使用FuncPrintSQL记录SQL
		PrintSQL: true,
	}

	// 根据dbDaoConfig创建dbDao, 一个数据库只执行一次,第一个执行的数据库为 defaultDao,后续zorm.xxx方法,默认使用的就是defaultDao
	dbDao, _ = zorm.NewDBDao(&dbDaoConfig)

	println("连接mysql成功。。。。。。。。。。。。。。。。。。。")
}

//TestInsert 02.测试保存Struct对象
func TestInsert(t *testing.T) {

	//需要手动开启事务,匿名函数返回的error如果不是nil,事务就会回滚
	_, err := zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {
		//创建一个demo对象
		//demo := newStudent()

		demo := Course{
			Id :zorm.FuncGenerateStringID(),
			Name : "体育",
			TeacherId : "01",
			CreateTime : time.Now(),
			UpdateTime : time.Now(),
		}

		//保存对象,参数是对象指针.如果主键是自增,会赋值到对象的主键属性
		_, err := zorm.Insert(ctx, &demo)

		//如果返回的err不是nil,事务就会回滚
		return nil, err
	})
	//标记测试失败
	if err != nil {
		// t.Errorf("错误:%v", err)
		println("mysql insert error：", err)
	}
}

//TestInsertEntityMap 03.测试保存EntityMap对象,用于不方便使用struct的场景,使用Map作为载体
func TestInsertEntityMap(t *testing.T) {

	//需要手动开启事务,匿名函数返回的error如果不是nil,事务就会回滚
	_, err := zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {
		//创建一个EntityMap,需要传入表名
		entityMap := zorm.NewEntityMap(teacherTableName)
		//设置主键名称
		entityMap.PkColumnName = "t_id"
		//如果是自增序列,设置序列的值
		//entityMap.PkSequence = "mySequence"

		//Set 设置数据库的字段值
		//如果主键是自增或者序列,不要entityMap.Set主键的值
		entityMap.Set("t_id", zorm.FuncGenerateStringID())
		entityMap.Set("t_name", "entityMap-userName")
		entityMap.Set("create_time", time.Now())
		entityMap.Set("update_time", time.Now())

		//执行
		_, err := zorm.InsertEntityMap(ctx, entityMap)

		//如果返回的err不是nil,事务就会回滚
		return nil, err
	})
	//标记测试失败
	if err != nil {
		t.Errorf("错误:%v", err)
	}
}

//TestQuery 04.测试查询一个struct对象
func TestQuery(t *testing.T) {

	//声明一个对象的指针,用于承载返回的数据
	demo := &Course{}

	//构造查询用的finder
	finder := zorm.NewSelectFinder(courseTableName) // select * from t_demo
	//finder = zorm.NewSelectFinder(demoStructTableName, "id,userName") // select id,userName from t_demo
	//finder = zorm.NewFinder().Append("SELECT * FROM " + demoStructTableName) // select * from t_demo

	//finder.Append 第一个参数是语句,后面的参数是对应的值,值的顺序要正确.语句统一使用?,zorm会处理数据库的差异
	finder.Append("WHERE c_id=?", "02")

	//执行查询
	err := zorm.Query(ctx, finder, demo)

	if err != nil { //标记测试失败
		t.Errorf("错误:%v", err)
	}
	//打印结果
	fmt.Println(demo)
}

//TestQueryMap 05.测试查询map接收结果,用于不太适合struct的场景,比较灵活
func TestQueryMap(t *testing.T) {

	//构造查询用的finder
	finder := zorm.NewSelectFinder(scoreTableName) // select * from t_demo
	//finder.Append 第一个参数是语句,后面的参数是对应的值,值的顺序要正确.语句统一使用?,zorm会处理数据库的差异
	finder.Append("WHERE c_id=? and s_score in(?)", "01", []int{76})
	//执行查询
	resultMap, err := zorm.QueryMap(ctx, finder)

	if err != nil { //标记测试失败
		t.Errorf("错误:%v", err)
	}
	//打印结果
	fmt.Println(resultMap)
}

//TestQuerySlice 06.测试查询对象列表
func TestQuerySlice(t *testing.T) {
	//创建用于接收结果的slice
	list := make([]Teacher, 0)

	//构造查询用的finder
	finder := zorm.NewSelectFinder(teacherTableName) // select * from t_demo
	//为了保证数据库迁移,分页语句必须要有order by
	finder.Append("order by createTime asc")

	//创建分页对象,查询完成后,page对象可以直接给前端分页组件使用
	page := zorm.NewPage()
	page.PageNo = 1    //查询第1页,默认是1
	page.PageSize = 20 //每页20条,默认是20

	//执行查询
	err := zorm.QuerySlice(ctx, finder, &list, page)
	if err != nil { //标记测试失败
		t.Errorf("错误:%v", err)
	}
	//打印结果
	fmt.Println("总条数:", page.TotalCount, "  列表:", list)
}

//TestQueryMapSlice 07.测试查询map列表,用于不方便使用struct的场景,一条记录是一个map对象
func TestQueryMapSlice(t *testing.T) {
	//构造查询用的finder
	finder := zorm.NewSelectFinder(studentTableName) // select * from t_demo
	//为了保证数据库迁移,分页语句必须要有order by
	finder.Append("order by createTime desc")

	//创建分页对象,查询完成后,page对象可以直接给前端分页组件使用
	page := zorm.NewPage()

	//执行查询
	listMap, err := zorm.QueryMapSlice(ctx, finder, page)
	if err != nil { //标记测试失败
		t.Errorf("错误:%v", err)
	}
	//打印结果
	fmt.Println("总条数:", page.TotalCount, "  列表:", listMap)
}

//TestUpdateNotZeroValue 08.更新struct对象,只更新不为零值的字段.主键必须有值
func TestUpdateNotZeroValue(t *testing.T) {

	//需要手动开启事务,匿名函数返回的error如果不是nil,事务就会回滚
	_, err := zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {
		//声明一个对象的指针,用于更新数据
		demo := &Teacher{}
		demo.Id = "b6cde578-b89e-481e-a6a8-93a10652c4b4"
		demo.Name = "Rust Actix-web"

		//更新 "sql":"UPDATE t_demo SET userName=? WHERE id=?","args":["UpdateNotZeroValue","41b2aa4f-379a-4319-8af9-08472b6e514e"]
		_, err := zorm.UpdateNotZeroValue(ctx, demo)

		//如果返回的err不是nil,事务就会回滚
		return nil, err
	})
	if err != nil { //标记测试失败
		t.Errorf("错误:%v", err)
	}

}

//TestUpdate 09.更新struct对象,更新所有字段.主键必须有值
func TestUpdate(t *testing.T) {

	//需要手动开启事务,匿名函数返回的error如果不是nil,事务就会回滚
	_, err := zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {

		//声明一个对象的指针,用于更新数据
		demo := &Student{}
		demo.Id = "9c5164a3-0f6a-46a6-8531-eafc2ab5dac5"
		demo.Name = "TestUpdate %%%%%%%$######"
		demo.CreateTime = time.Now()
		demo.UpdateTime = time.Now()

		_, err := zorm.Update(ctx, demo)

		//如果返回的err不是nil,事务就会回滚
		return nil, err
	})
	if err != nil { //标记测试失败
		t.Errorf("错误:%v", err)
	}
}

//TestUpdateFinder 10.通过finder更新,zorm最灵活的方式,可以编写任何更新语句,甚至手动编写insert语句
func TestUpdateFinder(t *testing.T) {
	//需要手动开启事务,匿名函数返回的error如果不是nil,事务就会回滚
	_, err := zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {
		finder := zorm.NewUpdateFinder(studentTableName) // UPDATE t_demo SET
		//finder = zorm.NewDeleteFinder(demoStructTableName)  // DELETE FROM t_demo
		//finder = zorm.NewFinder().Append("UPDATE").Append(demoStructTableName).Append("SET") // UPDATE t_demo SET
		finder.Append("s_name=?,s_sex=?", "TestUpdateFinder", "女").Append("WHERE s_id=?", "45d18258-d2cd-49f3-8847-fe55f4126e60")

		//更新 "sql":"UPDATE t_demo SET  userName=?,active=? WHERE id=?","args":["TestUpdateFinder",1,"41b2aa4f-379a-4319-8af9-08472b6e514e"]
		_, err := zorm.UpdateFinder(ctx, finder)

		//如果返回的err不是nil,事务就会回滚
		return nil, err
	})
	if err != nil { //标记测试失败
		t.Errorf("错误:%v", err)
	}

}

//TestUpdateEntityMap 11.更新一个EntityMap,主键必须有值
func TestUpdateEntityMap(t *testing.T) {
	//需要手动开启事务,匿名函数返回的error如果不是nil,事务就会回滚
	_, err := zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {
		//创建一个EntityMap,需要传入表名
		entityMap := zorm.NewEntityMap(courseTableName)
		//设置主键名称
		entityMap.PkColumnName = "c_id"
		//Set 设置数据库的字段值,主键必须有值
		entityMap.Set("c_id", "02")
		entityMap.Set("c_name", "数学")
		//更新 "sql":"UPDATE t_demo SET userName=? WHERE id=?","args":["TestUpdateEntityMap","41b2aa4f-379a-4319-8af9-08472b6e514e"]
		_, err := zorm.UpdateEntityMap(ctx, entityMap)

		//如果返回的err不是nil,事务就会回滚
		return nil, err
	})
	if err != nil { //标记测试失败
		t.Errorf("错误:%v", err)
	}

}

//TestDelete 12.删除一个struct对象,主键必须有值
func TestDelete(t *testing.T) {
	//需要手动开启事务,匿名函数返回的error如果不是nil,事务就会回滚
	_, err := zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {
		demo := &Student{}
		demo.Id = "9ce76bc9-8083-4b57-9659-69ce24ce7d7b"

		//删除 "sql":"DELETE FROM t_demo WHERE id=?","args":["9ce76bc9-8083-4b57-9659-69ce24ce7d7b"]
		_, err := zorm.Delete(ctx, demo)

		//如果返回的err不是nil,事务就会回滚
		return nil, err
	})
	if err != nil { //标记测试失败
		t.Errorf("错误:%v", err)
	}

}

// //TestOther 13.其他的一些说明.非常感谢您能看到这一行
// func TestOther(t *testing.T) {

// 	//场景1.多个数据库.通过对应数据库的dbDao,调用BindContextDBConnection函数,把这个数据库的连接绑定到返回的ctx上,然后把ctx传递到zorm的函数即可.
// 	newCtx, err := dbDao.BindContextDBConnection(ctx)
// 	if err != nil { //标记测试失败
// 		t.Errorf("错误:%v", err)
// 	}

// 	finder := zorm.NewSelectFinder(demoStructTableName).Append("order by id ")
// 	//把新产生的newCtx传递到zorm的函数
// 	list, _ := zorm.QueryMapSlice(newCtx, finder, nil)
// 	fmt.Println(list)

// 	//场景2.单个数据库的读写分离.设置读写分离的策略函数.
// 	zorm.FuncReadWriteStrategy = myReadWriteStrategy

// 	//场景3.如果是多个数据库,每个数据库还读写分离,按照 场景1 处理

// }

// //单个数据库的读写分离的策略 rwType=0 read,rwType=1 write
// func myReadWriteStrategy(rwType int) *zorm.DBDao {
// 	//根据自己的业务场景,返回需要的读写dao,每次需要数据库的连接的时候,会调用这个函数
// 	return dbDao
// }


type StudentController struct {
	Student *Student
}

func (sc *StudentController)QueryInfo(c *fiber.Ctx) {

	sc.Student = new(Student)
	res, err := sc.Student.QueryStudentInfo(c.Context())
	if err != nil {
		c.JSON(fiber.Map{
			"code":    500,
			"data":    nil,
			"message": err.Error(),
		})
		return
	}

	c.JSON(fiber.Map{
		"code":    200,
		"data":    res,
		"message": "成功",
	})
	return
}

func (sc *StudentController)UpdateInfo(c *fiber.Ctx) {

	sc.Student = new(Student)
	_, err := sc.Student.UpdateStudentInfo(c.Context())
	if err != nil {
		c.JSON(fiber.Map{
			"code":    500,
			"data":    nil,
			"message": err.Error(),
		})
		return
	}

	c.JSON(fiber.Map{
		"code":    200,
		"data":    nil,
		"message": "成功",
	})
	return
}

func (student *Student)QueryStudentInfo(wctx context.Context) (interface{}, error){
	// studentList := make([]Student, 0)

	sfinder := zorm.NewSelectFinder(studentTableName) // select * from t_demo
	sfinder.Append("order by create_time asc")

	page := zorm.NewPage()
	page.PageNo = 1    //查询第1页,默认是1
	page.PageSize = 100 //每页20条,默认是20

	// err := zorm.QuerySlice(wctx, sfinder, &studentList, page)

	studentMapList, err := zorm.QueryMapSlice(wctx, sfinder, page)
	if err != nil { //标记测试失败
		return nil, err
	}

	for _, s := range studentMapList{
		cfinder := zorm.NewFinder().Append("select c.* from course c inner join score s on c.c_id = s.c_id where s.s_id = ? order by c.create_time asc", s["s_id"])
		courseMapList, err := zorm.QueryMapSlice(wctx, cfinder, page)
		if err != nil { //标记测试失败
			return nil, err
		}
		s["course_list"] = courseMapList

		for _, c := range s["course_list"].([]map[string]interface{}){
			tfinder := zorm.NewFinder()
			tfinder.Append("select * from teacher where t_id = ?", c["t_id"])
			teacherMap, err := zorm.QueryMap(wctx, tfinder)
			if err != nil { //标记测试失败
				return nil, err
			}
			c["teacher"] = teacherMap

			ofinder := zorm.NewFinder()
			ofinder.Append("select * from score where c_id = ? and s_id = ?", c["c_id"], s["s_id"])
			scoreMap , err := zorm.QueryMap(wctx, ofinder)
			if err != nil { //标记测试失败
				return nil, err
			}
			c["score"] = scoreMap
		}
	}

	// for _, s := range studentList{

	// 	fmt.Println("s：", s.Id)

	// 	cfinder := zorm.NewFinder().Append("select c.* from course c inner join score s on c.c_id = s.c_id where s.s_id = ? order by c.createTime asc", s.Id)
	// 	zorm.QuerySlice(wctx, cfinder, &(&s).CourseList, page)
	// 	fmt.Println("CourseList：", s.CourseList)

	// 	for _, c := range s.CourseList{
	// 		tfinder := zorm.NewFinder()
	// 		tfinder.Append("select * from teacher where t_id = ?", c.TeacherId)
	// 		zorm.Query(wctx, tfinder, &(&c).Teacher)
	// 		fmt.Println("Teacher", c.Teacher)

	// 		ofinder := zorm.NewFinder()
	// 		ofinder.Append("select * from score where c_id = ? and s_id = ?", c.Id, s.Id)
	// 		zorm.Query(wctx, ofinder, &(&c).Score)
	// 		fmt.Println("Score", c.Score)
	// 	}
	// }

	return studentMapList, nil
}

func (student *Student)UpdateStudentInfo(wctx context.Context) (interface{}, error){

	_, err := zorm.Transaction(wctx, func(ctx context.Context) (interface{}, error) {

		var iduErr error

		//创建一个demo对象
		s := newStudent()
		//保存对象,参数是对象指针.如果主键是自增,会赋值到对象的主键属性
		_, iduErr = zorm.Insert(ctx, &s)

		t := &Teacher{}
		t.Id = "5148784a-a072-46ee-8ad5-741b88ae01c6"
		//删除 "sql":"DELETE FROM t_demo WHERE id=?","args":["9ce76bc9-8083-4b57-9659-69ce24ce7d7b"]
		_, iduErr = zorm.Delete(ctx, t)

		c := &Course{}
		c.Id = "936628cb-4455-42a8-9ed2-f184944fd114"
		c.Name = "gofiber zorm update"
		c.TeacherId = "b6cde578-b89e-481e-a6a8-93a10652c4b4"
		c.CreateTime = time.Now()
		c.UpdateTime = time.Now()
		_, iduErr = zorm.Update(ctx, c)

		u := &Course{}
		u.Id = "936628cb-4455-42a8-9ed2-f184944fd114"
		//删除 "sql":"DELETE FROM t_demo WHERE id=?","args":["9ce76bc9-8083-4b57-9659-69ce24ce7d7b"]
		_, iduErr = zorm.Delete(ctx, u)

		//如果返回的err不是nil,事务就会回滚
		return nil, iduErr
	})
	if err != nil { //标记测试失败
		return nil, err
	}

	return nil, nil
}

func main(){
	//TestInsert(&testing.T{})
	//TestInsertEntityMap(&testing.T{})
	// TestQuery(&testing.T{})
	// TestQueryMap(&testing.T{})
	// TestQuerySlice(&testing.T{})
	// TestQueryMapSlice(&testing.T{})
	// TestUpdateNotZeroValue(&testing.T{})
	// TestUpdate(&testing.T{})
	// TestUpdateFinder(&testing.T{})
	// TestUpdateEntityMap(&testing.T{})
	// TestDelete(&testing.T{})

	app := fiber.New()

	sc := new(StudentController)
	app.Post("/queryStudentInfo", sc.QueryInfo)
	app.Post("/updateStudentInfo", sc.UpdateInfo)

	app.Listen(":6264")
}
